﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Packaging;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Text;

namespace System
{
    public static class Global
    {
        private const string COMPONENT = ";component";
        private const string VERSION = "v";
        private const char COMPONENT_DELIMITER = ';';

        /// <summary>
        /// 生成当前程序集(直接调用此方法的程序集)的资源Uri
        /// </summary>
        /// <param name="relativeFile">相对于程序集根目录的路径</param>
        /// <returns></returns>
        public static Uri MakeCurrentPackUri(string relativeFile)
        {
            StackTrace trace = new StackTrace();
            var method = trace.GetFrame(1).GetMethod();
            return MakePackUri(method.DeclaringType.Assembly, relativeFile);
        }

        /// <summary>
        /// 生成指定程序集的资源Uri
        /// </summary>
        /// <param name="ass">目标程序集</param>
        /// <param name="relativeFile">相对于程序集根目录的路径</param>
        /// <returns></returns>
        public static Uri MakePackUri(Assembly ass, string relativeFile)
        {
            var assname = ass.ToString().Split(',')[0];
            StringBuilder uriString = new StringBuilder(); ;
#if !SILVERLIGHT 
            uriString.Append("pack://application:,,,");
#endif
            if (relativeFile.StartsWith("/"))
            {
                relativeFile = relativeFile.TrimStart('/');
            }
            uriString.Append("/" + assname + ";component/" + relativeFile);
            return new Uri(uriString.ToString(), UriKind.RelativeOrAbsolute);
        }
        /// <summary>
        /// 获取程序集中数据流资源的清单
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        public static string[] GetStreamResourcesManifest(Assembly assembly)
        {
            var allresnames = assembly.GetManifestResourceNames();
            List<string> list = new List<string>();
            foreach (string name in allresnames)
            {
                Stream st = assembly.GetManifestResourceStream(name);
                try
                {
                    using (ResourceReader resourceReader = new ResourceReader(st))
                    {
                        foreach (DictionaryEntry resourceEntry in resourceReader)
                        {
                            if (resourceEntry.Key is string relpath)
                            {
                                if (resourceEntry.Value is Stream stream)
                                {
                                    list.Add(relpath);
                                }
                            }
                        }
                    }
                }
                catch (ArgumentException)
                {
                    throw new Exception("读取资源清单文件出错。");
                }
            }
            return list.ToArray();
        }

        /// <summary>
        /// 获取Uri指向的程序集
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public static Assembly GetAssemblyFromPackUri(Uri uri)
        {
            Uri partUri = new Uri(uri.AbsolutePath, UriKind.Relative);

            GetAssemblyNameAndPart(partUri, out string partName, out string assemblyName, out string assemblyVersion, out string assemblyKey);

            if (String.IsNullOrEmpty(assemblyName))
            {

                throw new Exception("Uri中未指定程序集信息。");
            }
            else
            {
                return GetLoadedAssembly(new AssemblyName(assemblyName));
            }
        }
        #region Inner
        internal static void GetAssemblyNameAndPart(Uri uri, out string partName, out string assemblyName, out string assemblyVersion, out string assemblyKey)
        {
            string original = uri.ToString(); // only relative Uri here (enforced by Package)

            // Start and end points for the first segment in the Uri.
            int start = 0;
            int end;

            if (original[0] == '/')
            {
                start = 1;
            }

            partName = original.Substring(start);

            assemblyName = string.Empty;
            assemblyVersion = string.Empty;
            assemblyKey = string.Empty;

            end = original.IndexOf('/', start);

            string firstSegment = String.Empty;
            bool fHasComponent = false;

            if (end > 0)
            {
                // get the first section
                firstSegment = original.Substring(start, end - start);

                // The resource comes from dll
                if (firstSegment.EndsWith(COMPONENT, StringComparison.OrdinalIgnoreCase))
                {
                    partName = original.Substring(end + 1);
                    fHasComponent = true;
                }
            }

            if (fHasComponent)
            {
                string[] assemblyInfo = firstSegment.Split(new char[] { COMPONENT_DELIMITER });

                int count = assemblyInfo.Length;

                if ((count > 4) || (count < 2))
                {
                    throw new UriFormatException("");
                }

                //
                // if the uri contains escaping character,
                // Convert it back to normal unicode string
                // so that the string as assembly name can be
                // recognized by Assembly.Load later.
                //
                assemblyName = Uri.UnescapeDataString(assemblyInfo[0]);

                for (int i = 1; i < count - 1; i++)
                {
                    if (assemblyInfo[i].StartsWith(VERSION, StringComparison.OrdinalIgnoreCase))
                    {
                        if (string.IsNullOrEmpty(assemblyVersion))
                        {
                            assemblyVersion = assemblyInfo[i].Substring(1);  // Get rid of the leading "v"
                        }
                        else
                        {
                            throw new UriFormatException("");
                        }
                    }
                    else
                    {
                        if (string.IsNullOrEmpty(assemblyKey))
                        {
                            assemblyKey = assemblyInfo[i];
                        }
                        else
                        {
                            throw new UriFormatException("");
                        }
                    }
                } // end of for loop

            } // end of if fHasComponent
        }
        internal static Assembly GetLoadedAssembly(AssemblyName assemblyName)
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

            Version reqVersion = assemblyName.Version;
            CultureInfo reqCulture = assemblyName.CultureInfo;
            byte[] reqKeyToken = assemblyName.GetPublicKeyToken();

            for (int i = assemblies.Length - 1; i >= 0; i--)
            {
                AssemblyName curAsmName = new AssemblyName(assemblies[i].FullName);
                Version curVersion = curAsmName.Version;
                CultureInfo curCulture = curAsmName.CultureInfo;
                byte[] curKeyToken = curAsmName.GetPublicKeyToken();

                if ((String.Compare(curAsmName.Name, assemblyName.Name, true) == 0) &&
                     (reqVersion == null || reqVersion.Equals(curVersion)) &&
                     (reqCulture == null || reqCulture.Equals(curCulture)) &&
                     (reqKeyToken == null || IsSameKeyToken(reqKeyToken, curKeyToken)))
                {
                    return assemblies[i];
                }
            }
            return null;
        }
        static bool IsSameKeyToken(byte[] reqKeyToken, byte[] curKeyToken)
        {
            bool isSame = false;

            if (reqKeyToken == null && curKeyToken == null)
            {
                // Both Key Tokens are not set, treat them as same.
                isSame = true;
            }
            else if (reqKeyToken != null && curKeyToken != null)
            {
                // Both KeyTokens are set.
                if (reqKeyToken.Length == curKeyToken.Length)
                {
                    isSame = true;

                    for (int i = 0; i < reqKeyToken.Length; i++)
                    {
                        if (reqKeyToken[i] != curKeyToken[i])
                        {
                            isSame = false;
                            break;
                        }
                    }
                }
            }

            return isSame;
        }
        #endregion


    }
}
